﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text;
using System.IO;

namespace Milan.Diagnostics
{
	public static class log
	{
		private static Queue<string> m_lines = new Queue<string>(20);
		//private static int LogUnhandledExceptionFilter(IntPtr pExceptionInfo)
		//{
		//    string fileName = Milan.Windows.dbghelp.MiniDumpWriteDumpCurrent(pExceptionInfo);
		//    Error("Unhandled Exception written at " + fileName);
		//    return 1;
		//}
		static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
		{
			DumpError((Exception)e.ExceptionObject);
		}
		public static void Start()
		{
			AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
			//Milan.Windows.kernel32.SetUnhandledExceptionFilter(LogUnhandledExceptionFilter);
			Persistent("---START logging at " + DateTime.Now.ToLongTimeString() + " for " + System.Reflection.Assembly.GetEntryAssembly().Location);
		}
		public static void Stop()
		{
			DisplayLine("---STOP logging at " + DateTime.Now.ToLongTimeString() + " for " + System.Reflection.Assembly.GetEntryAssembly().Location);
		}
		/// <summary>
		/// This logs a critical error, which will be written to the critical error directory to be
		/// picked up by the reporter service.
		/// </summary>
		/// <param name="message">Message to write</param>
		public static void Critical(string message)
		{
			string fileName = Milan.Diagnostics.Pe.RetrieveLinkerTimestampAsString() + "-" + String.Format("X8", message.GetHashCode()) + ".err";
			System.IO.File.WriteAllText(fileName, message);
		}
		public static void Display(string segment)
		{
			foreach (ILog log in m_displayLogger)
				log.Write(segment, LogLevel.Display);
			if (Environment.UserInteractive)
			{
				if (Console.OpenStandardInput(1) != System.IO.Stream.Null)
					Console.Write(segment);
			}
		}
		public static void DisplayLine(string message = "")
		{
			Display(message + Environment.NewLine);
		}
		/// <summary>
		/// When an error is dumped, it is saved under a unique CRC for this error.
		/// The Reporter service will scan the send directory to see if this error has been sent.
		/// If not, it will 
		/// </summary>
		/// <param name="exception"></param>
		private static void DumpError(Exception exception)
		{
			// First build the error report
			StringBuilder contents = new StringBuilder();
			lock (m_lines)
			{
				while (m_lines.Count > 0)
					contents.AppendLine(m_lines.Dequeue());
			}
			contents.AppendLine("Exception:" + exception.Message);
			contents.AppendLine("Stack Trace:" + exception.StackTrace);
			// calculate the file name
			var fileName = Path.Combine(LogDirectory, contents.GetHashCode().ToString("x") + ".err");
			var text = contents.ToString();
			if(File.Exists(fileName))	// If the file exists, just touch the file time.
				File.SetLastWriteTimeUtc(fileName, DateTime.UtcNow);
			else						// If the file does not exist, create it, write it out to a file
				File.WriteAllText(fileName, text);
			Persistent(text);
		}
		public static void Error(string error)
		{
			WriteLine(error, LogLevel.Error);
		}
		public static void Error(Exception exception)
		{
			while (m_lines.Count > 0)
				PersistentLine(m_lines.Dequeue(), LogLevel.Display);
			PersistentLine("Exception:" + exception.Message, LogLevel.Error);
			PersistentLine("Stack Trace:" + exception.StackTrace, LogLevel.Error);
		}
		/// <summary>
		/// This log item is simply placed into the m_lines queue, but is not displayed unless a crash occurs.
		/// </summary>
		/// <param name="line">This is a line</param>
		public static void Hidden(string line)
		{
			m_lines.Enqueue(line);
		}
		public static void Persistent(string line)
		{
			WriteLine(line, LogLevel.Persistent);
		}
		public static void Warning(string warning)
		{
			WriteLine(warning, LogLevel.Warning);
		}
		private static void WriteLine(string line,LogLevel logLevel)
		{
#if DEBUG
			System.Diagnostics.Debug.WriteLine(DateTime.Now.ToString("HH:MM") + "=" + line);
#endif
			string fullLine = logLevel.ToString() + ":" + DateTime.Now.ToString("HH:mm") + "=" + line;
			DisplayLine(line);
			if (logLevel > LogLevel.Display)
				PersistentLine(line,logLevel);
			lock (m_lines)
			{
				if (m_lines.Count > 100)
					m_lines.Dequeue();
				m_lines.Enqueue(fullLine);
			}
		}
		private static void PersistentLine(string line, LogLevel logLevel)
		{
			if (m_persistentLogger != null && m_persistentLogger.Count > 0)
			{
				foreach( ILog log in m_persistentLogger )
					log.WriteLine(line, logLevel);
			}
			else
				System.IO.File.AppendAllText(CurrentLogFile, line + Environment.NewLine);
		}
		public static void RegisterDisplayLogger(ILog logger)
		{
			m_displayLogger.Add(logger);
		}
		public static void UnregisterDisplayLogger(ILog logger)
		{
			m_displayLogger.Remove(logger);
		}
		private static HashSet<ILog> m_displayLogger = new HashSet<ILog>();
		public static void RegisterPersistentLogger(ILog logger)
		{
			m_persistentLogger.Add(logger);
		}
		public static void UnregisterPersistentLogger(ILog logger)
		{
			m_persistentLogger.Remove(logger);
		}
		private static HashSet<ILog> m_persistentLogger = new HashSet<ILog>();
		private static DateTime m_lastLogTime;
		private static bool m_explicitlySet = false;
		public static string CurrentLogFile
		{
			get
			{
				DateTime tm = DateTime.Now.Date;
				if (!m_explicitlySet && tm != m_lastLogTime)
				{
					m_lastLogTime = tm;
					m_currentLogFile = null;
				}
				if (m_currentLogFile == null)
				{
					CreateAllAccessDirectory(LogDirectory);
					m_currentLogFile = System.IO.Path.Combine(LogDirectory, m_lastLogTime.ToString("yyyyMMdd") + ".log");
					Display( m_currentLogFile + Environment.NewLine);
				}
				return m_currentLogFile;
			}
			set
			{
				m_currentLogFile = value;
				CreateAllAccessDirectory(System.IO.Path.GetDirectoryName(value));
				m_explicitlySet = true;
				Display(m_currentLogFile + Environment.NewLine);
			}
		}
		private static string m_currentLogFile;
		public static string LogDirectory
		{
			get
			{
				if (m_logDirectory == null)
					m_logDirectory = Settings.LoggingFolder;
				return m_logDirectory;
			}
			set
			{
				m_logDirectory = value;
			}
		}
		private static string m_logDirectory;
		public static void CreateAllAccessDirectory(string directory)
		{
			var directoryInfo = System.IO.Directory.CreateDirectory(directory);
			var directorySecurity = directoryInfo.GetAccessControl();
			SecurityIdentifier securityIdentifier = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
			var rule = new FileSystemAccessRule(securityIdentifier, FileSystemRights.Write | FileSystemRights.ReadAndExecute | FileSystemRights.Modify, AccessControlType.Allow);
			bool modified;
			directorySecurity.ModifyAccessRule(AccessControlModification.Add, rule, out modified);
			directoryInfo.SetAccessControl(directorySecurity);
		}
	}
}
